import React, { ClassAttributes } from 'react';
import _ from 'lodash';
import classNames from 'classnames';
import * as SysDom from '../sys.dom';

let textBoxSeq = 0;

const nextTextBoxId = (): string => 'tb' + (textBoxSeq++);

interface ITextLiteralProps extends React.HTMLAttributes<HTMLSpanElement> {
    inline?: boolean,
    labelText?: string,
    value?: string,
}

const TextLiteral_ = (props: ITextLiteralProps) => {
    const { className, inline, value, labelText: labelText_, ...restProps } = props;

    const labelText = _.trim(labelText_);

    const label: JSX.Element = labelText && (<label style={{ wordBreak: 'keep-all' }} >{labelText}</label>);

    return <div className={classNames(className, { inline })} >
        {label}
        <div className="ui input" style={{ verticalAlign: 'baseline', borderStyle: 'none', borderBottomStyle: 'dotted', borderWidth: '1px' }}>
            <span style={{ wordBreak: 'keep-all' }} {...restProps}>{value || ''}</span>
        </div>
    </div>
};

TextLiteral_.defaultProps = {
    className: 'field',
}

const TextLiteral = React.memo(TextLiteral_);


interface ITextBoxProps extends React.InputHTMLAttributes<HTMLElement>, React.ClassAttributes<TextBox> {
    inline?: boolean,
    labelText?: string,
    prependText?: string,
    appendText?: string,
    icon?: string,
}

/**
 * 所有文本框的基础类。
 */
class TextBox<P extends ITextBoxProps = ITextBoxProps> extends React.Component<P> {
    private inputRef: React.RefObject<HTMLInputElement> = React.createRef();

    static defaultProps: ITextBoxProps = {
        type: 'text',
        className: 'field',
        inline: true,
    };

    setValue = (value?: string): TextBox<P> => {
        if (this.inputRef.current) { this.inputRef.current.value = value || ''; }

        return this;
    };

    getValue = (): string => this.inputRef.current ? this.inputRef.current.value : '';

    focus = (options?: FocusOptions): TextBox<P> => {
        if (this.inputRef.current) {
            this.inputRef.current.focus(options);
            this.inputRef.current.select();
        }

        return this;
    };

    shouldComponentUpdate = (nextProps: ITextBoxProps): boolean =>
        !_.isEqual(nextProps.labelText, this.props.labelText)
        || !_.isEqual(this.props.defaultValue, nextProps.defaultValue);

    protected handleChange = (evt: React.ChangeEvent<HTMLInputElement>): void => {
        if (this.props.onChange) {
            this.props.onChange(evt);
        }

        evt.preventDefault();
    };

    protected handleFocus = (evt: React.FocusEvent<HTMLInputElement>): void => {
        evt.target.select();

        evt.preventDefault();
    }

    render = (): JSX.Element => {
        return this.renderThis(this.props.type, this.props.defaultValue);
    };

    renderThis = (type: string, value: string | string[]): JSX.Element => {
        const { className, type: type_, labelText: labelText_, prependText: prependText_, appendText: appendText_, icon: icon_, ref, value: value_, onChange, ...restProps } = this.props;
        const labelText = _.trim(labelText_);
        const prependText = _.trim(prependText_);
        const appendText = _.trim(appendText_);
        const icon = _.trim(icon_);

        const id = nextTextBoxId();
        const label: JSX.Element = labelText && (<label htmlFor={id} style={{ wordBreak: 'keep-all' }} >{labelText}</label>);
        const prepend: JSX.Element = prependText && (<div className="ui label" >${this.props.prependText}</div>);
        const append: JSX.Element = appendText && (<div className="ui label" >${this.props.appendText}</div>);

        return <div className={classNames(className, { inline: this.props.inline })} >
            {label}
            <div className={classNames('ui input', { left: icon, icon })}>
                {prepend}
                {icon && <i className={'icon ' + icon} />}
                <input id={id} type={type} defaultValue={value} {...restProps} onChange={this.handleChange} onFocus={this.handleFocus} ref={this.inputRef} />
                {append}
            </div>
        </div>;
    };
};

/**
 * 密码框。
 */
class PasswordBox extends TextBox {
    render = (): JSX.Element => {
        return this.renderThis('password', this.props.defaultValue);
    };
}

/**
 * 搜索框。
 */
class SearchBox extends TextBox {
    render = (): JSX.Element => {
        return this.renderThis('search', this.props.defaultValue);
    };
}

/**
 * 手机号码输入框。
 */
class MobileBox extends TextBox {
    static defaultProps: ITextBoxProps = _.merge({}, TextBox.defaultProps, {
        minLength: 11,
        maxLength: 11,
        size: 11,
        pattern: '(111|13\d|15\d|17\d|18\d)\d8'
    });

    render = (): JSX.Element => {
        return this.renderThis('tel', this.props.defaultValue);
    };
}

/**
 * 电子邮件输入框。
 */
class EmailBox extends TextBox {
    render = (): JSX.Element => {
        return this.renderThis('email', this.props.defaultValue);
    };
}

interface INumberBoxProps extends ITextBoxProps {
    numberValue?: number | string,
    fractionDigits?: number,
}

class NumberBox extends TextBox<INumberBoxProps> {
    static defaultProps: INumberBoxProps = _.merge({}, TextBox.defaultProps, {
        numberValue: null,
        fractionDigits: 0,
    });

    getNumberValue = (): number => SysDom.parseNumber(this.getValue(), this.props.fractionDigits!)

    shouldComponentUpdate = (nextProps: INumberBoxProps): boolean =>
        !_.isEqual(nextProps.labelText, this.props.labelText)
        || !_.isEqual(this.props.numberValue, nextProps.numberValue);

    render = (): JSX.Element => {
        return this.renderThis('number', SysDom.formatNumber(this.props.numberValue, this.props.fractionDigits!));
    };
}

interface IDateBoxProps extends ITextBoxProps {
    type?: SysDom.DateType,
    dateValue?: Date,
}

class DateBox extends TextBox<IDateBoxProps> {
    static defaultProps: IDateBoxProps = _.merge({}, TextBox.defaultProps, {
        type: 'date' as SysDom.DateType,
        dateValue: null,
    });

    getDateValue = () => SysDom.parseDate(this.getValue(), this.props.type!);

    shouldComponentUpdate = (nextProps: IDateBoxProps): boolean =>
        !_.isEqual(nextProps.labelText, this.props.labelText)
        || !_.isEqual(this.props.dateValue, nextProps.dateValue);

    render = (): JSX.Element => {
        return this.renderThis(this.props.type, SysDom.formatDate(this.props.dateValue, this.props.type!));
    };
}

interface INumberRangeGroupProps extends React.HTMLAttributes<HTMLDivElement>, ClassAttributes<NumberRangeGroup> {
    inline?: boolean,
    labelText?: string,
    minPlaceholder?: string,
    maxPlaceholder?: string,
    min?: number,
    max?: number,
    step?: number,
    minNumberValue?: number,
    maxNumberValue?: number,
    fractionDigits?: number,
}

/**
 * 数字范围输入框。
 */
class NumberRangeGroup extends React.Component<INumberRangeGroupProps> {
    inputRef1: React.RefObject<HTMLInputElement> = React.createRef();
    inputRef2: React.RefObject<HTMLInputElement> = React.createRef();

    private static PREDEFINED_STEPS = [1, 0.1, 0.01, 0.001, 0.0001];

    static defaultProps: INumberRangeGroupProps = {
        inline: true,
        className: 'field',
        labelText: '',
        min: null,
        max: null,
        step: null,
        minNumberValue: null,
        maxNumberValue: null,
        fractionDigits: 0,
    };

    getValue = (): [number, number] => {
        const v1 = _.trim(this.inputRef1.current.value);
        const v2 = _.trim(this.inputRef2.current.value);

        return [SysDom.parseNumber(v1, this.props.fractionDigits), SysDom.parseNumber(v2, this.props.fractionDigits)]
    };

    setValue = (d1: number, d2: number): NumberRangeGroup => {
        if (this.inputRef1.current && _.isNumber(d1)) {
            this.inputRef1.current.value = SysDom.formatNumber(this.props.minNumberValue, this.props.fractionDigits!);
        }
        if (this.inputRef2.current && _.isNumber(d2)) {
            this.inputRef2.current.value = SysDom.formatNumber(this.props.maxNumberValue, this.props.fractionDigits!);
        }

        return this;
    };

    private handleFocus = (evt: React.FocusEvent<HTMLInputElement>): void => evt.target.select();

    shouldComponentUpdate = (nextProps: INumberRangeGroupProps): boolean => !_.isEqual(nextProps, this.props);

    render = (): JSX.Element => {
        const { labelText: labelText_, min, max, step: step_, minPlaceholder, maxPlaceholder, minNumberValue, maxNumberValue, fractionDigits, onChange, className, ref, ...restProps } = this.props;
        const id1 = nextTextBoxId();
        const id2 = nextTextBoxId();

        const labelText = _.trim(labelText_);
        const step = step_ !== null ? step_ : NumberRangeGroup.PREDEFINED_STEPS[fractionDigits];

        return <div className={classNames(className, { inline: this.props.inline })} {...restProps}>
            {labelText && <label htmlFor={id1} style={{ wordBreak: 'keep-all' }}  >{labelText}</label>}
            <div className="ui labeled input">
                <input id={id1} type="number" placeholder={minPlaceholder}
                    min={min} max={max} step={step} defaultValue={SysDom.formatNumber(minNumberValue, fractionDigits!)}
                    ref={this.inputRef1} onFocus={this.handleFocus} />
                <div className="ui label">-</div>
                <input id={id2} type="number" placeholder={maxPlaceholder}
                    min={min} max={max} step={step} defaultValue={SysDom.formatNumber(maxNumberValue, fractionDigits!)}
                    ref={this.inputRef2} onFocus={this.handleFocus} />
            </div>
        </ div>;
    }
};

interface IDateRangeGroupProps extends React.HTMLAttributes<HTMLDivElement>, ClassAttributes<DateRangeGroup> {
    inline?: boolean,
    labelText?: string,
    type: SysDom.DateType,
    minPlaceholder?: string,
    maxPlaceholder?: string,
    minDateValue?: Date,
    maxDateValue?: Date,
}

/**
 * 日期范围输入框。
 */
class DateRangeGroup extends React.Component<IDateRangeGroupProps> {
    inputRef1: React.RefObject<HTMLInputElement> = React.createRef();
    inputRef2: React.RefObject<HTMLInputElement> = React.createRef();

    static defaultProps: IDateRangeGroupProps = {
        inline: true,
        className: 'field',
        labelText: '',
        type: 'date',
        minDateValue: null,
        maxDateValue: null,
    };

    getValue = (): [Date, Date] => {
        const v1 = _.trim(this.inputRef1.current.value);
        const v2 = _.trim(this.inputRef2.current.value);

        return [SysDom.parseDate(v1, this.props.type), SysDom.parseDate(v2, this.props.type)];
    };

    setValue = (d1: number, d2: number): DateRangeGroup => {
        if (this.inputRef1.current && _.isNumber(d1)) {
            this.inputRef1.current.value = SysDom.formatDate(this.props.minDateValue, this.props.type);
        }
        if (this.inputRef2.current && _.isNumber(d2)) {
            this.inputRef2.current.value = SysDom.formatDate(this.props.maxDateValue, this.props.type);
        }

        return this;
    };

    private handleFocus = (evt: React.FocusEvent<HTMLInputElement>): void => evt.target.select();

    shouldComponentUpdate = (nextProps: IDateRangeGroupProps): boolean => !_.isEqual(nextProps, this.props);

    render = (): JSX.Element => {
        const { type, labelText: labelText_, minPlaceholder, maxPlaceholder, minDateValue, maxDateValue, onChange, className, ref, ...restProps } = this.props;

        const id1 = nextTextBoxId();
        const id2 = nextTextBoxId();

        const labelText = _.trim(labelText_);

        return <div className={classNames(className, { inline: this.props.inline })} {...restProps}>
            {labelText && <label htmlFor={id1} style={{ wordBreak: 'keep-all' }}  >{labelText}</label>}
            <div className="ui labeled input">
                <input id={id1} type={type} placeholder={minPlaceholder}
                    defaultValue={SysDom.formatDate(this.props.minDateValue, type)} ref={this.inputRef1} onFocus={this.handleFocus} />
                <div className="ui label">-</div>
                <input id={id2} type={type} placeholder={maxPlaceholder}
                    defaultValue={SysDom.formatDate(this.props.maxDateValue, type)} ref={this.inputRef2} onFocus={this.handleFocus} />
            </div>
        </div>;
    }
};

export { TextLiteral, TextBox, PasswordBox, SearchBox, MobileBox, EmailBox, NumberBox, DateBox, NumberRangeGroup, DateRangeGroup };
